package com.yexx.starter.security.basic;

import com.yexx.core.constant.CommonConstants;
import com.yexx.core.exception.CommonException;
import com.yexx.starter.security.basic.constant.SecurityConstants;
import com.yexx.starter.security.basic.util.AuthUtils;
import com.yexx.starter.security.basic.util.JwtUtils;
import com.yexx.uid.IdGenerator;
import com.yexx.uid.SnowFlakeIdGenerator;
import com.yexx.utils.CacheUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * Description: 登录验证拦截
 *
 * @author: zuomin (myleszelic@outlook.com)
 * @date: 2020/11/02-14:35
 */
public class ApiAuthenticationFilter extends BasicAuthenticationFilter {

    private CacheUtil cacheUtil;

    //单点登录
    private Boolean singleSignOn = false;

    public ApiAuthenticationFilter(AuthenticationManager authenticationManager, CacheUtil cacheUtil, Boolean singleSignOn) {
        super(authenticationManager);
        this.cacheUtil = cacheUtil;
        this.singleSignOn = singleSignOn;
    }

    /**
     * 判断请求是否是否带有token信息，token是否合法，是否过期。设置安全上下文。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        String tokenHeader = request.getHeader(SecurityConstants.TOKEN_HEADER);
        String tokenPar = request.getParameter(SecurityConstants.TOKEN_PARAM);

        String token = null;

        //可能是登录或者注册的请求，不带token信息，又或者是不需要登录，不需要token即可访问的资源。
        if (!StringUtils.isEmpty(tokenHeader)) {
            token = tokenHeader;
            token = URLDecoder.decode(token, StandardCharsets.UTF_8);
            if (!token.startsWith(SecurityConstants.HEAD)) {
                throw new CommonException(SecurityConstants.RETURN_CODE.MISS_BEARER, "miss bearer");
            }
            token = token.substring(SecurityConstants.HEAD.length());
        } else if (!StringUtils.isEmpty(tokenPar)) {
            token = tokenPar;
        } else {
            throw new CommonException(SecurityConstants.RETURN_CODE.MISS_TOKEN, "miss token");
        }

        Claims claims = JwtUtils.getClaim(token);
        if (claims == null) {
            throw new CommonException(SecurityConstants.RETURN_CODE.ERROR_TOKEN, "token error");
        }

        String userIdStr = claims.getSubject();
        if (userIdStr == null) {
            throw new CommonException(SecurityConstants.RETURN_CODE.INVALID_TOKEN, "invalid token");
        }

        Date expiredTime = claims.getExpiration();
        if (expiredTime == null || (System.currentTimeMillis() > expiredTime.getTime())) {
            throw new CommonException(SecurityConstants.RETURN_CODE.TOKEN_EXPIRE, "token expire");
        }

        String tokenKey = AuthUtils.getTokenKey(userIdStr, token);
        UserContext context = (UserContext) cacheUtil.get(tokenKey);
        if (context == null) {
            throw new CommonException(SecurityConstants.RETURN_CODE.TOKEN_EXPIRE, "token expire");
        }

        if (singleSignOn && context.getSiginElsewhere()) {
            //这个时候可以直接退登了.也可以自行调用登出api
            throw new CommonException(SecurityConstants.RETURN_CODE.SIGN_IN_ELSEWHERE, "sign in elsewhere");
        }

        context.setTokenKey(tokenKey);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(context, null, context.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        logger.info("authenticated opt_user " + userIdStr + ", setting security context");
        SecurityContextHolder.getContext().setAuthentication(authentication);
        setTrace(request, response);
        chain.doFilter(request, response);

    }

    IdGenerator idGenerator = new SnowFlakeIdGenerator(1, 1);

    private void setTrace(HttpServletRequest request, HttpServletResponse response) {
        Long traceId = idGenerator.generate();
        logger.info("traceId -->{}" + traceId);
        request.setAttribute(CommonConstants.LOG.TRACE_ID_ATTR, traceId.toString());
    }

    public void setCacheUtil(CacheUtil cacheUtil) {
        this.cacheUtil = cacheUtil;
    }

}
